package au.edu.utas.yu_qiao_group.luckyman.service;

import android.database.sqlite.SQLiteDatabase;
import android.icu.text.SimpleDateFormat;
import android.text.TextUtils;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import au.edu.utas.yu_qiao_group.luckyman.db.draws.DrawDTO;
import au.edu.utas.yu_qiao_group.luckyman.db.draws.DrawTable;
import au.edu.utas.yu_qiao_group.luckyman.db.raffles.RaffleDTO;
import au.edu.utas.yu_qiao_group.luckyman.db.raffles.RaffleDrawDTO;
import au.edu.utas.yu_qiao_group.luckyman.db.raffles.RaffleTable;
import au.edu.utas.yu_qiao_group.luckyman.db.tickets.TicketDTO;
import au.edu.utas.yu_qiao_group.luckyman.db.tickets.TicketTable;
import au.edu.utas.yu_qiao_group.luckyman.utils.RandomUtils;

public class TicketService {
  public static ArrayList<TicketDTO> retrieveAllByRaffleID(SQLiteDatabase db, long id) {
    return TicketTable.getAllByRaffleID(db, id);
  }

  public static List<String> getCustomerRecord(SQLiteDatabase db) {
    ArrayList<TicketDTO> l = null;
    try {
      l = TicketTable.getCustomerDistinct(db);
    } catch (Exception e) {}
    return l == null ? new ArrayList<String>() : l.stream().map(i -> i.getMCustomerPhone()).collect(Collectors.toList());
  }

  public static boolean updateTicket(SQLiteDatabase db, TicketDTO t) throws Exception {
    boolean result = false;
    try {
      TicketTable.update(db, t);
      result = true;
    } catch (Exception e) {
      throw new Exception("edit failed");
    }
    return result;
  }

  public static boolean batchInsertTicket(SQLiteDatabase db, RaffleDTO r, String customerPhone, int batchNum) throws Exception {
    if (r.getMTicketRemain() < batchNum) {
      throw new Exception("no enough remain tickets");
    }
    DrawDTO d = DrawTable.getByRaffID(db, r.getMRaffleID());
    if (d == null) {
      throw new Exception("no draw found!");
    }
    if (d.getMStatus() == DrawDTO.STATUS_FINISHED || d.getMStatus() == DrawDTO.STATUS_CANCELLED) {
      throw new Exception("draw status error!");
    }
    ArrayList<TicketDTO> purchasedTickets = TicketTable.getAllByCustomerPhone(db, r, customerPhone);
    if (r.getMSingleLimit() - purchasedTickets.size() < batchNum) {
      throw new Exception("single limitation overflowed!");
    }
    ArrayList<TicketDTO> existingTickets = TicketTable.getAllByRaffleID(db, r.getMRaffleID());
    SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddhhmmss");
    String orderNo = formatter.format(new Date()) + RandomUtils.getRandom(6);
    db.beginTransaction();
    for (int i = 0; i < batchNum; i++) {
      if (insertTicket(db, r, existingTickets, customerPhone, orderNo) == false) {
        db.endTransaction();
        throw new Exception("insert failed");
      }
    }
    try {
      RaffleTable.updateTicketRemain(db, r, r.getMTicketRemain() - batchNum);
    } catch (Exception e) {
      db.endTransaction();
      throw new Exception("update ticket remain failed");
    }
    db.setTransactionSuccessful();
    db.endTransaction();
    return true;
  }


  public static boolean insertTicket(SQLiteDatabase db, RaffleDTO r, ArrayList<TicketDTO> existingTickets, String customerPhone, String orderNo) {
    boolean result = false;
    boolean isRandom = r.getMDrawMethod() == 1;
    String ticketNo = null;
//    int insertIndex = existingTickets.size() - 1; // for updating the existing list
    Set<String> ticketNoSet = new HashSet<String>();
    for (Iterator<TicketDTO> it = existingTickets.iterator(); it.hasNext(); ) {
      ticketNoSet.add(it.next().getMTicketNo());
    }
    if (isRandom) {
      // method: 1 margin-raffle
      ticketNo = generateRandomTicketNo(TicketTable.TICKET_NO_LENGTH, ticketNoSet);
    } else {
      // method: 0 normal raffle
      ticketNo = generateSquentialTicketNo(TicketTable.TICKET_NO_LENGTH, ticketNoSet);
      if (TextUtils.isEmpty(ticketNo)) {
        return false;
      }
    }
    TicketDTO t = new TicketDTO();
    t.setMRaffleID(r.getMRaffleID());
    t.setMTicketNo(ticketNo);
    t.setMOrderNo(orderNo);
    t.setMCreateTime(new Date().getTime());
    t.setMCustomerPhone(customerPhone);
    t.setMPurchasePrice(r.getMUnitPrice());
    try {
      TicketTable.insert(db, t);
      existingTickets.add(t);
      result = true;
    } catch (Exception e) {
    }
    return result;
  }

  public static boolean delTicket(SQLiteDatabase db, TicketDTO t, RaffleDrawDTO r) {
    boolean result = false;
    db.beginTransaction();
    try {
      TicketTable.delete(db, t);
      RaffleTable.updateTicketRemain(db, r, r.getMTicketRemain() + 1);
      db.setTransactionSuccessful();
      result = true;
    } catch (Exception e) {
    } finally {
      db.endTransaction();
    }
    return result;
  }

  private static String generateRandomTicketNo(int len, Set<String> exclude) {
    String newTicketNo = RandomUtils.getRandom(len - 1);
    newTicketNo = (newTicketNo == "000" ? "1" : "0") + newTicketNo;
    while (exclude.contains(newTicketNo)) {
      newTicketNo = RandomUtils.getRandom(len - 1);
      newTicketNo = (newTicketNo == "000" ? "1" : "0") + newTicketNo;
    }
    return newTicketNo;
  }

  private static String generateSquentialTicketNo(int len, Set<String> exclude) {
    int ticketNo = 1;
    Set<Integer> excludeInts = exclude.stream().map(i -> Integer.parseInt(i)).collect(Collectors.toSet());
    // sequential
    while (excludeInts.contains(ticketNo)) {
      ticketNo++;
    }
    String newTicketNo = String.valueOf(ticketNo);
    // leftpad zero
    while (newTicketNo.length() < TicketTable.TICKET_NO_LENGTH) {
      newTicketNo = "0" + newTicketNo;
    }
    // generate failed
    if (newTicketNo.length() > TicketTable.TICKET_NO_LENGTH) {
      return "";
    }
    return newTicketNo;
  }
}
